package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/build"
	"go/importer"
	"go/parser"
	"go/printer"
	"go/token"
	"go/types"
	"gopp"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"time"

	"golang.org/x/tools/go/ast/astutil"

	"github.com/thoas/go-funk"
	"github.com/twmb/algoimpl/go/graph"
)

type ParserContext struct {
	path          string
	wkdir         string // for cgo
	outdir        string // temporary C files, middle files dir, like ./opkgs/
	pkgrename     string
	builtin_psctx *ParserContext
	cber          *cbuilder

	fset   *token.FileSet
	pkgs   map[string]*ast.Package
	files  []*ast.File
	typkgs *types.Package
	conf   types.Config

	chkerrs    []error
	chkwarns   []error
	chkunkerrs []error
	chkfatals  []error

	info     types.Info
	cursors  map[ast.Node]*astutil.Cursor
	grstargs map[string]bool // goroutines packed arguments structure

	typeDeclsm    map[string]*ast.TypeSpec
	typeDeclsv    []*ast.TypeSpec
	funcDeclsm    map[string]*ast.FuncDecl
	funcDeclsv    []*ast.FuncDecl
	funcdeclNodes map[string]graph.Node
	initFuncs     []*ast.FuncDecl
	functypes     map[ast.Expr]string               // *ast.FuncType => tmptyname
	fnexcepts     map[*ast.FuncDecl]*FuncExceptions // =>

	cpr2      *cparser2
	fcpkg     *types.Package  // fakec package, generated by this Object
	gomangles map[string]bool // c struct need typedef struct name struct_name;
	ctypes    map[ast.Expr]types.Type
	cidents   map[*ast.Ident]types.TypeAndValue // 传播其他位置引用c类型返回值的变量的类型
	tmpvars   map[ast.Stmt][]ast.Node           // => value node
	gostmts   []*ast.GoStmt
	chanops   []ast.Expr // *ast.SendStmt
	closures  []*ast.FuncLit
	tupletys  map[string]*tupleinfo // tuple string => tmptyname
	defers    []*ast.DeferStmt
	globvars  []ast.Node            // => ValueSpec node
	kvpairs   map[ast.Node]ast.Node // left <=> value

	gb       *graph.Graph // decl depgraph in one package
	bdpkgs   *build.Package
	ccode    string
	fcdefscc string // fake C defs content
}

func NewParserContext(path string, pkgrename string, builtin_psctx *ParserContext) *ParserContext {
	this := &ParserContext{}
	this.path = path
	this.pkgrename = pkgrename
	this.builtin_psctx = builtin_psctx

	this.info.Types = make(map[ast.Expr]types.TypeAndValue)
	this.info.Defs = make(map[*ast.Ident]types.Object)
	this.info.Uses = make(map[*ast.Ident]types.Object)
	this.info.Scopes = make(map[ast.Node]*types.Scope)
	this.info.Implicits = make(map[ast.Node]types.Object)

	this.cursors = make(map[ast.Node]*astutil.Cursor)
	this.grstargs = make(map[string]bool)
	this.typeDeclsm = make(map[string]*ast.TypeSpec)
	this.funcDeclsm = make(map[string]*ast.FuncDecl)
	this.funcdeclNodes = make(map[string]graph.Node)
	this.initFuncs = make([]*ast.FuncDecl, 0)
	this.functypes = make(map[ast.Expr]string)
	this.fnexcepts = make(map[*ast.FuncDecl]*FuncExceptions)

	this.fcpkg = fcpkg
	this.gb = graph.New(graph.Directed)

	return this
}

// semachk parse but no semantics check: types.Check
func (this *ParserContext) Init(semachk bool) error {
	return this.Init_no_cgocmd(semachk)
	// return this.Init_explict_cgo()
}

func (this *ParserContext) Init_no_cgocmd(semachk bool) error {

	bdpkgs, err := build.ImportDir(this.path, build.ImportComment)
	gopp.ErrPrint(err)
	this.bdpkgs = bdpkgs
	if len(bdpkgs.InvalidGoFiles) > 0 {
		log.Fatalln("Have InvalidGoFiles", bdpkgs.InvalidGoFiles)
	}
	log.Println(this.path, bdpkgs.Name, bdpkgs.GoFiles, bdpkgs.TestGoFiles,
		bdpkgs.CgoFiles, bdpkgs.CFiles, bdpkgs.CXXFiles)

	// parser step 2, got ast
	this.fset = token.NewFileSet()
	pkgs, err := parser.ParseDir(this.fset, this.path, this.dirFilter, 0|parser.AllErrors|parser.ParseComments)
	gopp.ErrPrint(err)
	this.pkgs = pkgs
	gopp.Assert(len(pkgs) == 1, "wtttt", len(pkgs), this.path)
	this.ccode = this.pickCCode()

	cp2 := newcparser2(bdpkgs.Name)
	ccodefile := "./opkgs/" + bdpkgs.Name + "_embed_code.c"
	err = ioutil.WriteFile(ccodefile, []byte(this.ccode), 0644)
	gopp.ErrPrint(err)
	//defer os.Remove(ccodefile)
	err = cp2.parsefile(ccodefile)
	gopp.ErrFatal(err, ",", bdpkgs.Name)
	this.cpr2 = cp2
	// os.Exit(-1)
	this.cber.embedccode(this.pickCCode2())

	this.walkpass_valid_files()
	if semachk { // improve speed
		this.walkpass_dotransforms(false)
		this.saveastcode()
		// os.Exit(-1)
	}

	this.walkpass_fill_funcvars()
	this.walkpass_flat_cursors()
	// this.walkpass_prefill_ctypes() // before types.Config.Check
	this.walkpass_fill_fakecpkg()   // before types.Config.Check
	this.walkpass_fill_builtinpkg() // before types.Config.Check
	// parser step 3, got types, semantics check
	if !semachk {
		return nil
	}
	this.walkpass_check() // semantics check
	if this.chkerrs != nil {
		log.Fatalln("chkerrs", len(this.chkerrs))
	}

	// this.walkpass_dotransforms(true)
	// this.walkpass_resolve_ctypes()
	// this.walkpass_flat_cursors() // move move previous
	// this.walkpass_clean_cgodecl()
	this.walkpass_flat_cursors()
	this.walkpass_func_deps()
	log.Println("pkgs", this.typkgs.Name(), "types:", len(this.info.Types),
		"typedefs", len(this.typeDeclsm), "funcdefs", len(this.funcDeclsm))

	this.walkpass_tmpvars()
	this.walkpass_kvpairs()
	this.walkpass_gostmt()
	this.walkpass_chan_send_recv()
	this.walkpass_closures()
	this.walkpass_tupletys()
	this.walkpass_defers()
	this.walkpass_globvars()
	this.walkpass_functypes()
	this.walkpass_fnexcepts()

	return err
}
func (pc *ParserContext) pkgimperror(err error) {
	if err == nil {
		return
	}
	chkwarns := []error{}
	chkunks := []error{}
	_ = chkwarns
	_ = chkunks
	// warnerrs := []string{}
	// fatalerrs := []string{}
	// must stop error
	if strings.Contains(err.Error(), "could not import") ||
		strings.Contains(err.Error(), "no result values expected") ||
		strings.Contains(err.Error(), "missing return") ||
		strings.Contains(err.Error(), "has no field or method") ||
		strings.Contains(err.Error(), "variable) is not a type") ||
		strings.Contains(err.Error(), "invalid statement") ||
		strings.Contains(err.Error(), "not declared by package") ||
		strings.Contains(err.Error(), "wrong number of return values") ||
		strings.Contains(err.Error(), "redeclared in this block") ||
		strings.Contains(err.Error(), "invalid operation: mismatched types") ||
		//strings.Contains(err.Error(), "undeclared name:") ||
		false {
		pc.chkerrs = append(pc.chkerrs, err)
		pc.chkfatals = append(pc.chkfatals, err)
		if len(pc.chkfatals) >= 3 {
			log.Fatalln("fatalerr", err)
		} else {
			log.Println("fatalerr", err)
		}
	} else if // TODO
	strings.Contains(err.Error(), "(type) is not an expression") ||
		false {
		log.Println("warn5err", err)
	} else if strings.Contains(err.Error(), "declared but not used") ||
		strings.Contains(err.Error(), "not exported by package C") ||
		strings.Contains(err.Error(), "too many arguments") ||
		strings.Contains(err.Error(), "not exported by package") ||
		false {
		// log.Println(err)
		chkwarns = append(chkwarns, err)
		pc.chkwarns = append(pc.chkwarns, err)
	} else {
		log.Println("unkerr", err)
		chkunks = append(chkunks, err)
		pc.chkunkerrs = append(pc.chkunkerrs, err)
	}
}
func (this *ParserContext) Init_explict_cgo() error {
	gopp.Assert(1 == 2, "waitdep")
	bdpkgs, err := build.ImportDir(this.path, build.ImportComment)
	gopp.ErrPrint(err)
	this.bdpkgs = bdpkgs
	if len(bdpkgs.InvalidGoFiles) > 0 {
		log.Fatalln("Have InvalidGoFiles", bdpkgs.InvalidGoFiles)
	}
	// use go-clang to resolve c function signature
	// extract c code from bdpkgs.CgoFiles
	// parser step 1, got raw cgo c code
	{
		this.fset = token.NewFileSet()

		pkgs, err := parser.ParseDir(this.fset, this.path, this.dirFilter, 0|parser.AllErrors|parser.ParseComments)
		gopp.ErrPrint(err)
		this.pkgs = pkgs
		this.ccode = this.pickCCode()
	}

	this.walkpass_cgo_processor()
	// replace codebase dir
	this.path, this.wkdir = this.wkdir, this.path
	bdpkgs, err = build.ImportDir(this.path, build.ImportComment)
	gopp.ErrPrint(err)
	this.bdpkgs = bdpkgs
	if len(bdpkgs.InvalidGoFiles) > 0 {
		log.Fatalln("Have InvalidGoFiles", bdpkgs.InvalidGoFiles)
	}
	log.Println(bdpkgs.GoFiles)

	// parser step 2, got ast/types
	this.fset = token.NewFileSet()
	pkgs, err := parser.ParseDir(this.fset, this.path, this.dirFilter, 0|parser.AllErrors|parser.ParseComments)
	gopp.ErrPrint(err)
	this.pkgs = pkgs

	// this.ccode = this.pickCCode()
	this.walkpass_valid_files()

	this.walkpass_check()

	this.walkpass_clean_cgodecl()
	this.walkpass_resolve_ctypes_bycgo()
	this.walkpass_flat_cursors()
	this.walkpass_func_deps()
	log.Println("pkgs", this.typkgs.Name(), "types:", len(this.info.Types),
		"typedefs", len(this.typeDeclsm), "funcdefs", len(this.funcDeclsm))

	this.walkpass_tmpvars()
	this.walkpass_kvpairs()
	this.walkpass_gostmt()
	this.walkpass_chan_send_recv()
	this.walkpass_closures()
	this.walkpass_tupletys()
	this.walkpass_defers()
	this.walkpass_globvars()

	return err
}

// cgo preprocessor
func (pc *ParserContext) walkpass_cgo_processor() {
	gopp.Assert(1 == 2, "waitdep")
	pc.wkdir = "_obj"
	{
		err := os.RemoveAll(pc.wkdir + "/")
		gopp.ErrPrint(err, pc.wkdir)
		os.Mkdir(pc.wkdir, 0755)
	}
	goexe, err := exec.LookPath("go")
	gopp.ErrPrint(err)
	cmdfld := []string{goexe, "tool", "cgo", "-objdir", pc.wkdir}
	bdpkgs := pc.bdpkgs
	for _, cgofile := range bdpkgs.CgoFiles {
		cgofile = bdpkgs.Dir + "/" + cgofile
		cmdfld = append(cmdfld, cgofile)
	}
	log.Println(cmdfld)
	if len(bdpkgs.CgoFiles) > 0 {
		cmdo := exec.Command(cmdfld[0], cmdfld[1:]...)
		allout, err := cmdo.CombinedOutput()
		gopp.ErrPrint(err, cmdfld)
		allout = bytes.TrimSpace(allout)
		if len(allout) > 0 {
			log.Println(string(allout))
		}
		if err != nil {
			os.Exit(-1)
		}
	}

	// copy orignal source to wkdir
	// remove cgofile from wkdir
	files, err := filepath.Glob(bdpkgs.Dir + "/*")
	gopp.ErrPrint(err)
	for _, file := range files {
		if funk.Contains(bdpkgs.CgoFiles, filepath.Base(file)) {
			continue
		}
		err = gopp.CopyFile(file, pc.wkdir+"/"+filepath.Base(file))
		gopp.ErrPrint(err, file)
	}
	os.Rename(pc.wkdir+"/_cgo_gotypes.go", pc.wkdir+"/cxuse_cgo_gotypes.go")
}

func (pc *ParserContext) saveastcode() {
	pkgs := pc.pkgs
	codebuf := bytes.NewBuffer(nil)
	for _, pkgo := range pkgs {
		for _, fio := range pkgo.Files {
			err := printer.Fprint(codebuf, pc.fset, fio)
			// err := ast.Fprint(codebuf, pc.fset, nil, nil)
			gopp.ErrPrint(err, pc.bdpkgs.Name)
		}
	}
	log.Println(pc.bdpkgs.Name, codebuf.Len())
	savefile := fmt.Sprintf("./opkgs/%s-ast-tfed.go", pc.bdpkgs.Name)
	err := ioutil.WriteFile(savefile, codebuf.Bytes(), 0644)
	gopp.ErrPrint(err, savefile, codebuf.Len())
}

func (pc *ParserContext) walkpass_check() {
	pc.conf.IgnoreFuncBodies = false
	pc.conf.DisableUnusedImportCheck = true
	pc.conf.Error = pc.pkgimperror
	pc.conf.FakeImportC = true
	pc.conf.FakeImportC = false
	pc.conf.Importer = &mypkgimporter{pc.bdpkgs.Name, pc.fcpkg}

	files := pc.files
	// files = append(files, pc.fakecfile())
	var err error
	pc.typkgs, err = pc.conf.Check(pc.path, pc.fset, files, &pc.info)
	pc.pkgimperror(err)
	log.Println("pkgcomplete", pc.typkgs.Name(), pc.typkgs.Complete())
}

func (this *ParserContext) nameFilter2(filename string, files []string) bool {
	for _, okfile := range files {
		if filename == okfile {
			return true // keep
		}
	}
	return false
}
func (this *ParserContext) nameFilter(filename string) bool {
	if this.nameFilter2(filename, this.bdpkgs.GoFiles) {
		return true
	}
	if this.nameFilter2(filename, this.bdpkgs.CgoFiles) {
		return true
	}
	return false
}
func (this *ParserContext) dirFilter(f os.FileInfo) bool {
	return this.nameFilter(f.Name())
}

type mypkgimporter struct {
	curpkg string
	fcpkg  *types.Package
}

func (this *mypkgimporter) Import(path string) (pkgo *types.Package, err error) {
	log.Println(this.curpkg, "importing ...", path)
	if path == "C" {
		return this.fcpkg, nil
	}
	// in xgo/src/
	if path == "unsafe" {
	}
	if true {
		// go 1.12
		fset := token.NewFileSet()
		pkgo, err = importer.ForCompiler(fset, "source", nil).Import(path)
	} else {
		pkgo, err = importer.Default().Import(path)
	}
	gopp.ErrPrint(err, path, this.curpkg)
	return pkgo, err
}

func trimgopath(filename string) string {
	gopaths := gopp.Gopaths()

	for _, pfx := range gopaths {
		if strings.HasPrefix(filename, pfx) {
			return filename[len(pfx)+5:]
		}
	}
	return filename
}
func exprpos(pc *ParserContext, e ast.Node) token.Position {
	if e == nil {
		return token.Position{}
	}
	poso := pc.fset.Position(e.Pos())
	poso.Filename = trimgopath(poso.Filename)
	return poso
}

func (this *ParserContext) pickCCode() string {
	rawcode := this.pickCCode2()
	lines := strings.Split(rawcode, "\n")
	rawcode = ""
	for _, line := range lines {
		line2 := strings.TrimSpace(line)
		if !strings.HasPrefix(line2, "#cgo ") {
			rawcode += line + "\n"
		} else {
			rawcode += "// " + line + "\n"
		}
	}
	// log.Println("got c code", rawcode)
	return rawcode
}
func (this *ParserContext) pickCCode2() string {
	ccode := ""
	for _, f := range this.bdpkgs.CgoFiles {
		var fo *ast.File = this.findFileobj(f)
		ccode += this.pickCCode3(fo)
	}
	return ccode
}
func (this *ParserContext) pickCCode3(fo *ast.File) string {
	for idx, cmto := range fo.Comments {
		// isimpcblock(cmto)???
		for idx2, impo := range fo.Imports {
			gopp.G_USED(idx, idx2)
			if impo.Pos()-token.Pos(len("\nimport ")) == cmto.End() {
				// log.Println("got c code", cmto.Text())
				cmtpfx := "// embed C code from " + exprpos(this, impo).String() + "\n"
				return cmtpfx + cmto.Text()
			}
		}
	}
	return ""
}
func (this *ParserContext) findFileobj(fbname string) *ast.File {
	for _, pkgo := range this.pkgs {
		for filename, fileo := range pkgo.Files {
			name := filepath.Base(filename)
			if name == fbname {
				return fileo
			}
		}
	}
	return nil
}

func (pc *ParserContext) walkpass_resolve_ctypes_bycgo() {
	gopp.Assert(1 == 2, "waitdep")
	pkgs := pc.pkgs

	ctypes := map[ast.Expr]types.Type{}
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Expr:
				csty := pc.info.TypeOf(te)
				if csty != nil && !isinvalidty2(csty) {
				} else {
					log.Println("unfixty", te, reftyof(te), csty)
				}
				if strings.Contains(exprstr(te), "_Ctype_") ||
					strings.Contains(exprstr(te), "_Cfunc_") {
					log.Println("cgo ", reftyof(te), exprstr(te), csty)
				}
			case *ast.Ident:
			}
			return true
		}, func(c *astutil.Cursor) bool {
			return true
		})
	}
	pc.ctypes = ctypes
}

type csyminfo struct {
	idt      *ast.Ident
	varo     *types.Var
	funo     *types.Func
	isfunc   bool
	isconst  bool
	isstruct bool
	isfield  bool
	istype   bool
}

func newcsyminfo(idt *ast.Ident, varo *types.Var) *csyminfo {
	csi := &csyminfo{}
	csi.idt = idt
	csi.varo = varo
	return csi
}

func (csi *csyminfo) isvar() bool {
	return !csi.isfunc && !csi.isconst &&
		!csi.isstruct && !csi.isfield && !csi.istype
}
func (csi *csyminfo) String() string {
	s := csi.idt.Name
	s += gopp.IfElseStr(csi.isfunc, " func", "")
	s += gopp.IfElseStr(csi.isstruct, " struct", "")
	s += gopp.IfElseStr(csi.isfield, " field", "")
	s += gopp.IfElseStr(csi.isconst, " const", "")
	s += gopp.IfElseStr(csi.istype, " type", "")
	s += gopp.IfElseStr(csi.isvar(), " var?", "")

	return s
}

/*
算法说明：
* 收集C.xyz 符号，并根据其出现位置，初步判断其是，变量，常量，类型，函数
* 在cparser符号表中查表对应的符号，进一步确定符号类别
* 把符号声明写到fakec包。只带有类型即可，如常量变量不用给值，函数只需要返回值类型
*/

// before types.Config.Check
func (pc *ParserContext) walkpass_fill_fakecpkg() {
	pkgs := pc.pkgs

	fcpkg := pc.fcpkg
	fcpkg.MarkComplete()
	fcscope := fcpkg.Scope()

	tmpidts2 := map[*ast.Ident]*csyminfo{}
	inconst := false

	intmpidts2 := func(idt *ast.Ident) *csyminfo {
		for t, csi := range tmpidts2 {
			if t.Name == idt.Name {
				return csi
			}
		}
		return nil
	}
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.GenDecl:
				if te.Tok == token.CONST {
					inconst = true
				} else {
					inconst = false
				}
			case *ast.SelectorExpr:
				if iscident(te.X) {
					pn := c.Parent()
					var atye ast.Expr
					if fe, ok := pn.(*ast.Field); ok {
						atye = fe.Type
					}
					if fe, ok := pn.(*ast.CallExpr); ok { // sizeof(C.pthread_mutex_t)
						if false {
							log.Println(fe.Args[0], reftyof(fe.Args[0]))
						}
					} else if fe, ok := pn.(*ast.CompositeLit); ok {
						atye = fe.Type
					} else if fe, ok := pn.(*ast.StarExpr); ok {
						// var x *C.Foo
						_ = fe
					}
					istype := atye == te // 是否是在type的位置上
					log.Println("got111", te.X, te.Sel, c.Index(), inconst, reftyof(pn), atye, reftyof(atye), reftyof(pn), atye == te, istype, exprpos(pc, te))
					csi := newcsyminfo(te.Sel, nil)
					csi.istype = istype
					csi.isconst = inconst
					if ocsi := intmpidts2(te.Sel); ocsi != nil {
						ocsi.istype = ocsi.istype || istype
					} else {
						tmpidts2[te.Sel] = csi
					}

				}
			case *ast.CallExpr:
				fo := te.Fun
				if fe, ok := fo.(*ast.SelectorExpr); ok {
					if iscident(fe.X) {
						// log.Println("got222", exprstr(fe.X), fe.X, fe.Sel)
						var cfnobj *types.Func
						if csi := intmpidts2(fe.Sel); csi != nil {
							if csi.isfunc {
							} else {
								log.Panicln("wtfff", fe.Sel, csi.isfunc, csi)
							}
						} else {
							csi := newcsyminfo(fe.Sel, nil)
							csi.isfunc = true
							csi.funo = cfnobj
							tmpidts2[fe.Sel] = csi
						}
					}
				}
			case *ast.ValueSpec:
				// in const
				var sele *ast.SelectorExpr
				if fe, ok := te.Type.(*ast.StarExpr); ok {
					if se, ok := fe.X.(*ast.SelectorExpr); ok {
						sele = se // var x *C.Foo
					}
				} else if fe, ok := te.Type.(*ast.SelectorExpr); ok {
					sele = fe // var x C.Foo
				}
				if sele != nil {
					if iscident(sele.X) {
						csi := newcsyminfo(sele.Sel, nil)
						csi.istype = true
						csi.isconst = inconst
						if ocsi := intmpidts2(sele.Sel); ocsi != nil {
							ocsi.istype = ocsi.istype || true
						} else {
							tmpidts2[sele.Sel] = csi
						}
					}
				}
			case *ast.TypeSpec:
				log.Println(te.Name, te.Type)
				if fe, ok := te.Type.(*ast.SelectorExpr); ok {
					if iscident(fe.X) {
						csi := newcsyminfo(fe.Sel, nil)
						csi.istype = true
						csi.isconst = inconst
						if ocsi := intmpidts2(fe.Sel); ocsi != nil {
							ocsi.istype = ocsi.istype || true
						} else {
							tmpidts2[fe.Sel] = csi
						}
					}
				}
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.GenDecl:
				if te.Tok == token.CONST {
					inconst = false
				}
			}
			return true
		})
	}
	log.Println(len(fcscope.Names()), fcscope.Names())

	struct_gomangles := map[string]bool{}
	cnter := -1
	for idt, csi := range tmpidts2 {
		cnter++
		idtname := idt.Name
		log.Println(cnter, idtname, csi.String())
		log.Println("gen fakectype", csi.idt)
		ctystr, ctyobj, _ := pc.cpr2.symtype(idtname)
		log.Println(cnter, "/", csi.String(), "/", ctystr, "/", ctyobj)

		if strings.HasPrefix(ctystr, "struct ") {
			// log.Println(idtname, ctyobj, ctystr)
			ctyobj3 := ctyobj
			if ctyobj2, ok := ctyobj.(*types.Pointer); ok {
				ctyobj3 = ctyobj2.Elem()
			}
			fcscope.Insert(ctyobj3.(*types.Named).Obj())
			struct_gomangles[ctyobj3.String()[2:]] = true // strip C.
		}
		if csi.isfunc {
			funo := fakecfunc2(newIdent(idtname), fcpkg, ctyobj)
			fcscope.Insert(funo)
			// fnty := funo.Type().(*types.Signature)
		} else if csi.isconst {
			cono := fakecconst2(newIdent(idtname), fcpkg, ctyobj)
			fcscope.Insert(cono)
		} else if strings.HasPrefix(idtname, cstruct_) {
			log.Println(idtname, ctystr, ctyobj)
			fcscope.Insert(ctyobj.(*types.Named).Obj())
			struct_gomangles[idtname] = true
			log.Println(idtname, ctystr, ctyobj)
		} else if csi.istype {
			switch te := ctyobj.(type) {
			case *types.Named:
				fcscope.Insert(te.Obj())
				log.Println(te.Obj().Name(), idtname)
				if idtname != te.Obj().Name() {
					// need an alias type $idtname $te.Obj().Name()
					tyname1 := types.NewTypeName(token.NoPos, fcpkg, idtname, ctyobj)
					fcscope.Insert(tyname1)
				}
			default:
				tyty := fakectype(newIdent(idtname), fcpkg, ctyobj)
				fcscope.Insert(tyty.Obj())
			}
		} else if csi.isvar() {
			varo := fakecvar2(newIdent(idtname), fcpkg, ctyobj)
			fcscope.Insert(varo)
		} else {
			log.Println("todo", cnter, csi.String(), ctystr, ctyobj)
		}

	}
	pc.gomangles = struct_gomangles
	log.Println(pc.bdpkgs.Name, len(fcscope.Names()), fcscope.Names(), len(struct_gomangles))
	if filepath.Base(pc.path) == "unsafe" {
		return
	}

	buf := bytes.NewBuffer(nil)
	buf.WriteString(pc.path + "\n")
	fcscope.WriteTo(buf, 1, true)
	pc.fcdefscc = "// " + strings.ReplaceAll(string(buf.Bytes()), "\n", "\n// ")
	cdefsfile := fmt.Sprintf("./opkgs/%s.cdefs", pc.bdpkgs.Name)
	ioutil.WriteFile(cdefsfile, buf.Bytes(), 0644)
	if nilcnt := strings.Count(string(buf.Bytes()), "<nil>"); nilcnt > 0 {
		log.Println(pc.bdpkgs.Name, "still have unresolved symbols", nilcnt)
	}
}

// before types.Config.Check
func (pc *ParserContext) walkpass_fill_builtinpkg() {
	if pc.builtin_psctx == nil {
		return
	}
	pkgs := pc.pkgs
	bipc := pc.builtin_psctx
	bidefs := map[string]bool{}
	for idt, obj := range bipc.info.Defs {
		if obj == nil {
			continue // package ident
		}
		isglob := isglobalid(bipc, idt)
		if !isglob {
			continue
		}
		bidefs[idt.Name] = true
	}

	for _, pkg := range pkgs {
		for _, fio := range pkg.Files {
			var gendecl *ast.GenDecl
			for _, d := range fio.Decls {
				switch dd := d.(type) {
				case *ast.GenDecl:
					gendecl = dd
				}
			}
			if gendecl == nil {
				gendecl = &ast.GenDecl{}
				fio.Decls = append(fio.Decls, gendecl)
			}

			iSpec := newimpspec("xgo/builtin", "")
			gendecl.Specs = append(gendecl.Specs, iSpec)
			ast.SortImports(pc.fset, fio)
		}

		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.Ident:
				obj := te.Obj
				if obj != nil {
					break
				}
				if te.Name == pkg.Name {
					break
				}
				_, inbi := bidefs[te.Name]
				if !inbi {
					break
				}
				if _, ok := c.Parent().(*ast.SelectorExpr); ok {
					break
				}
				if _, ok := c.Parent().(*ast.Ident); ok {
					break
				}
				if _, ok := c.Parent().(*ast.FuncDecl); ok {
					break
				}
				if funk.Contains([]string{"len", "cap"}, te.Name) {
					break
				}
				log.Println(te, obj == nil, obj, reftyof(c.Parent()))
				sele := &ast.SelectorExpr{}
				sidt := newIdent("builtin")
				sele.X = sidt
				sele.Sel = te
				c.Replace(sele)

				// 把builtin的包ident添加上包前缀
			case *ast.CallExpr:
				if fnidt, ok := te.Fun.(*ast.Ident); ok {
					if fnidt.Name == "errreturn" ||
						fnidt.Name == "nilreturn" {
						// replace to if (err != nil) { return }
						retstmt := &ast.ReturnStmt{}
						retstmt.Return = te.Pos()
						for i := 1; i < len(te.Args); i++ {
							retstmt.Results = append(retstmt.Results, te.Args[i])
						}
						blkstmt := &ast.BlockStmt{}
						blkstmt.Lbrace = te.Pos()
						blkstmt.List = append(blkstmt.List, retstmt)
						binexpr := &ast.BinaryExpr{}
						binexpr.OpPos = te.Pos()
						binexpr.Op = token.NEQ
						if fnidt.Name == "nilreturn" {
							binexpr.Op = token.EQL
						}
						binexpr.X = te.Args[0]
						binexpr.Y = nilidtcon
						ifstmt := &ast.IfStmt{}
						ifstmt.If = te.Pos()
						ifstmt.Cond = binexpr
						ifstmt.Body = blkstmt
						// c.Replace(ifstmt)
						stmt1 := pc.cursors[c.Parent()]
						stmt2 := pc.cursors[stmt1.Parent()]
						if blkstmt2, ok := stmt2.Node().(*ast.BlockStmt); ok {
							for idx, be := range blkstmt2.List {
								if be == stmt1.Node() {
									blkstmt2.List[idx] = ifstmt
								}
							}
						}
					}
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}

func (pc *ParserContext) walkpass_resolve_ctypes() {
	gopp.Assert(1 == 2, "waitdep")
	pc.walkpass_resolve_ctypes1()
	for e, t := range pc.ctypes {
		log.Println(exprstr(e), t)
	}
	pc.walkpass_resolve_ctypes2()
	pc.walkpass_resolve_ctypes3()
}
func (pc *ParserContext) walkpass_resolve_ctypes1() {
	gopp.Assert(1 == 2, "waitdep")
	pkgs := pc.pkgs

	ctypes := map[ast.Expr]types.Type{}
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Expr:
				csty := pc.info.TypeOf(te)
				if csty != nil && !isinvalidty2(csty) {
					break
				}

				switch se := te.(type) {
				case *ast.SelectorExpr:
					if iscsel(se) {
						caty := &canytype{}
						caty.name = se.Sel.Name + "__ctype"
						tyandval := types.TypeAndValue{} //caty, nil}
						tyandval.Type = caty
						pc.info.Types[se] = tyandval
						pc.info.Types[se.Sel] = tyandval
						ctypes[se] = caty
						pn := c.Parent()
						// log.Println(reftyof(pn))
						switch pe := pn.(type) {
						case *ast.CallExpr:
							pc.info.Types[pe] = tyandval
							ctypes[pe] = caty
							// pe2 := pc.cursors[pe].Parent()
							// if _, ok := pe2.(*ast.StarExpr); ok {
							// pc.info.Types[pe2] =
							//}
						}
						// ctypes[c.Parent().(ast.Expr)] = caty
						// log.Println("fix some", te, reftyof(te), csty, ",", caty)
					} else {
						log.Println("fixty", te, reftyof(te), csty)
					}
				default:
					log.Println(te, reftyof(te), csty)
				}
			}
			return true
		}, func(c *astutil.Cursor) bool {
			return true
		})
	}
	pc.ctypes = ctypes
}
func (pc *ParserContext) walkpass_resolve_ctypes2() {
	gopp.Assert(1 == 2, "waitdep")
	pkgs := pc.pkgs

	// 需要一个带scope 的 AST 遍历
	pc.cidents = map[*ast.Ident]types.TypeAndValue{}
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.AssignStmt:
				for idx, le := range te.Lhs {
					csty := pc.info.TypeOf(le)
					if csty != nil && !isinvalidty2(csty) {
						break
					}
					re := te.Rhs[idx]
					csty2 := pc.info.TypeOf(re)
					if csty2 != nil && !isinvalidty2(csty2) {
						log.Println(idx, "canfixty", le, reftyof(le), csty2)
						tyandval := types.TypeAndValue{} //caty, nil}
						tyandval.Type = csty2
						pc.info.Types[le] = tyandval
						pc.ctypes[le] = tyandval.Type
						if idt, ok := le.(*ast.Ident); ok {
							pc.cidents[idt] = tyandval
						}
					} else {
						csty3 := pc.ctypes[re]
						if csty3 != nil {
							log.Println(idx, "canfixty", le, reftyof(le), csty2)
							tyandval := types.TypeAndValue{Type: csty2} //caty, nil}
							pc.info.Types[le] = tyandval
							pc.ctypes[le] = tyandval.Type
							if idt, ok := le.(*ast.Ident); ok {
								pc.cidents[idt] = tyandval
							}
						} else {
							log.Println(idx, "cannot fixty", le, reftyof(le), csty2, csty3, exprstr(re), reftyof(re))
						}
					}
				}
			case *ast.ValueSpec:
				for _, name := range te.Names {
					csty := pc.info.TypeOf(name)
					if csty != nil && !isinvalidty2(csty) {
						break
					}
					log.Println("untyval", reftyof(name), exprstr(name))
				}
			case *ast.Ident:
				if te.Name == "C" {
					break
				}
				csty := pc.info.TypeOf(te)
				if csty != nil && !isinvalidty2(csty) {
					break
				}
				incidt := pc.cidents[te] // 名字相同，但实例不同的ident无法匹配
				for idt, idty := range pc.cidents {
					if idt.Name == te.Name {
						if te.Obj != nil && te.Obj == idt.Obj {
							incidt = idty
						}
						// log.Println("same name ident", idty, idt == te, incidt, te.Obj == idt.Obj)
					}
				}
				if incidt.Type != nil {
					pc.info.Types[te] = incidt
					pc.ctypes[te] = incidt.Type
					log.Println("fixtyidt", reftyof(te), exprstr(te), incidt, len(pc.cidents))
				} else {
					log.Println("untyidt", reftyof(te), exprstr(te), incidt, len(pc.cidents))
				}
			case *ast.BinaryExpr:
				csty := pc.info.TypeOf(te)
				if csty != nil && !isinvalidty2(csty) {
					break
				}
				tyx := pc.info.TypeOf(te.X)
				tyy := pc.info.TypeOf(te.Y)
				var tyres types.Type
				if isinvalidty2(tyx) || isinvalidty2(tyy) {
					log.Println("not possible?")
				}
				if !isinvalidty2(tyx) {
					tyres = tyx
				}
				if !isinvalidty2(tyy) {
					tyres = tyy
				}
				pc.info.Types[te] = types.TypeAndValue{Type: tyres}
				pc.ctypes[te] = tyres
				if isinvalidty2(tyx) {
					pc.info.Types[te.X] = types.TypeAndValue{Type: tyres}
					pc.ctypes[te.X] = tyres
				}
				if isinvalidty2(tyy) {
					pc.info.Types[te.Y] = types.TypeAndValue{Type: tyres}
					pc.ctypes[te.Y] = tyres
				}

				log.Println("fixed untybinop", reftyof(te), exprstr(te), len(pc.cidents), tyx, tyy)

			case ast.Expr:
			}
			return true
		})
	}

}
func (pc *ParserContext) walkpass_resolve_ctypes3() {
	gopp.Assert(1 == 2, "waitdep")
	pkgs := pc.pkgs

	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Expr:
				csty := pc.info.TypeOf(te)
				if csty != nil && !isinvalidty2(csty) {
					break
				}
				log.Println("unresolvty", reftyof(te), exprstr(te))
			}
			return true
		})
	}

}

func (pc *ParserContext) walkpass_valid_files() {
	this := pc
	pkgs := pc.pkgs

	var files []*ast.File
	for _, pkg := range pkgs {
		for _, file := range pkg.Files {
			if strings.HasSuffix(file.Name.Name, "_test") {
				continue
			}
			files = append(files, file)
		}
	}
	this.files = files
}

func (pc *ParserContext) walkpass_func_deps() {
	pc.walkpass_func_deps1()
	pc.walkpass_func_deps2()
}
func (pc *ParserContext) walkpass_func_deps1() {
	this := pc
	pkgs := pc.pkgs

	pc.putFuncCallDependcy("main", "main_go")
	for _, pkg := range pkgs {
		var curfds []string // stack, current func decls
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.TypeSpec:
				// log.Println("typedef", t.Name.Name)
				this.typeDeclsm[te.Name.Name] = te
			case *ast.FuncDecl:
				if te.Recv != nil && te.Recv.NumFields() > 0 {
					varty := te.Recv.List[0].Type
					if ve, ok := varty.(*ast.StarExpr); ok {
						varty2 := ve.X
						tyname := varty2.(*ast.Ident).Name
						fnfullname := tyname + "_" + te.Name.Name
						this.funcDeclsm[fnfullname] = te
						curfds = append(curfds, fnfullname)
					} else if ve, ok := varty.(*ast.Ident); ok {
						tyname := ve.Name
						fnfullname := tyname + "_" + te.Name.Name
						this.funcDeclsm[fnfullname] = te
						curfds = append(curfds, fnfullname)
					} else {
						log.Println("todo", varty, reflect.TypeOf(te.Recv.List[0]))
					}
				} else {
					if te.Name.Name == "init" {
						this.initFuncs = append(this.initFuncs, te)
					}
					this.funcDeclsm[te.Name.Name] = te
					curfds = append(curfds, te.Name.Name)
				}
			case *ast.CallExpr:
				if len(curfds) == 0 { // global scope call
					switch be := te.Fun.(type) {
					case *ast.SelectorExpr:
						if iscsel(be.X) {
							break
						} else {
							log.Println("wtf", te, te.Fun, reflect.TypeOf(te.Fun))
						}
					default:
						log.Println("wtf", te, te.Fun, reflect.TypeOf(te.Fun))
					}
					// break
				} else {
					var curfd = curfds[len(curfds)-1]
					switch be := te.Fun.(type) {
					case *ast.Ident:
						this.putFuncCallDependcy(curfd, be.Name)
					case *ast.SelectorExpr:
						if iscsel(be.X) {
							break
						}
						varty := this.info.TypeOf(be.X)
						tyname := sign2rety(varty.String())
						tyname = strings.TrimRight(tyname, "*")
						fnfullname := tyname + "_" + be.Sel.Name
						this.putFuncCallDependcy(curfd, fnfullname)
					default:
						log.Println("todo", te.Fun, reflect.TypeOf(te.Fun))
					}
				}
			case *ast.Ident: // func name referenced
				if len(curfds) == 0 {
					break
				}
				var curfd = curfds[len(curfds)-1]
				varobj := this.info.ObjectOf(te)
				switch varobj.(type) {
				case *types.Func:
					this.putFuncCallDependcy(curfd, te.Name)
				}
			case *ast.ReturnStmt:
			case *ast.CompositeLit:
				if len(curfds) == 0 {
					log.Println("todo globvar", exprpos(pc, c.Node()))
					break
				}
				var curfd = curfds[len(curfds)-1]
				goty := pc.info.TypeOf(te.Type)
				for funame, fd := range pc.funcDeclsm {
					if fd.Recv.NumFields() == 0 {
						continue
					}
					rcv0 := fd.Recv.List[0]
					rcvty := pc.info.TypeOf(rcv0.Type)
					samety := rcvty == goty
					if ptrty, ok := rcvty.(*types.Pointer); ok && !samety {
						samety = ptrty.Elem() == goty
					}
					if samety {
						this.putFuncCallDependcy(curfd, funame)
					}
				}
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncDecl:
				if te.Recv != nil && te.Recv.NumFields() > 0 {
					curfds = curfds[:len(curfds)-1]
				} else {
					curfds = curfds[:len(curfds)-1]
				}
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}
func (pc *ParserContext) walkpass_func_deps2() {
	nodes := pc.gb.TopologicalSort()
	for _, node := range nodes {
		pc.funcDeclsv = append(pc.funcDeclsv, pc.funcDeclsm[(*node.Value).(string)])
	}
	// unused decls
	for _, d := range pc.funcDeclsm {
		if _, ok := builtinfns[d.Name.Name]; ok {
			continue
		}
		invec := false
		for _, d1 := range pc.funcDeclsv {
			if d1 == d {
				invec = true
				break
			}
		}
		if !invec {
			pc.funcDeclsv = append(pc.funcDeclsv, d)
		}
	}
}

func (pc *ParserContext) walkpass_fill_funcvars() {
	pkgs := pc.pkgs
	var toperrvarobj *ast.Ident
	var jmpfidxvarobj *ast.Ident
	var errlnovarobj *ast.Ident
	_ = toperrvarobj
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncDecl:
				if te.Body == nil {
					break
				}
				if te.Body == nil || te.Body.List == nil {
					break
				}

				toperrvar, declvar := newVardecl("gxtvtoperr", newIdent("error"), te.Body.Pos())
				te.Body.List = append([]ast.Stmt{declvar}, te.Body.List...)
				toperrvarobj = toperrvar
				jmpfidxvar, declvar2 := newVardecl("gxjmpfromidx", newIdent("int"), te.Body.Pos())
				te.Body.List = append([]ast.Stmt{declvar2}, te.Body.List...)
				jmpfidxvarobj = jmpfidxvar

				errlnovar, declvar3 := newVardecl("gxtvtoperr_lineno", newIdent("int"), te.Body.Pos())
				te.Body.List = append([]ast.Stmt{declvar3}, te.Body.List...)
				errlnovarobj = errlnovar

				// add return if not have
				lastmt := te.Body.List[len(te.Body.List)-1]
				if _, ok := lastmt.(*ast.ReturnStmt); !ok {
					res := te.Type.Results
					needretarg := res != nil
					if res != nil && res.NumFields() > 0 {
						needretarg = len(res.List[0].Names) == 0
					}
					// log.Println(te.Name, needretarg, res)
					if needretarg {
						var rargs []ast.Expr
						for _, fld := range res.List {
							idt, declvar := newVardecl(tmpvarname(), fld.Type, lastmt.Pos())
							rargs = append(rargs, idt)
							te.Body.List = append(te.Body.List, declvar)
						}
						retst := &ast.ReturnStmt{}
						retst.Results = rargs
						te.Body.List = append(te.Body.List, retst)
					} else {
						retst := &ast.ReturnStmt{}
						te.Body.List = append(te.Body.List, retst)
					}
				}

			case *ast.CatchStmt:
				assign := &ast.AssignStmt{}
				assign.TokPos = te.Pos()
				assign.Tok = token.DEFINE
				err2idt := newIdent("err")
				err2idt.Obj = ast.NewObj(ast.Var, err2idt.Name)
				err2idt.NamePos = te.Pos()
				assign.Lhs = append(assign.Lhs, err2idt)
				assign.Rhs = append(assign.Rhs, toperrvarobj)
				gopp.Assert(toperrvarobj != nil, "wtfff")
				te.Init = assign
				te.Tag = assign.Lhs[0]
				log.Println(te, toperrvarobj == nil)

				// erridx := gxjmpfromidx
				erridxidt := newIdent("erridx")
				erridxidt.Obj = ast.NewObj(ast.Var, erridxidt.Name)
				erridxidt.NamePos = te.Pos()
				assign.Lhs = append(assign.Lhs, erridxidt)
				assign.Rhs = append(assign.Rhs, jmpfidxvarobj)

				// errlno := gxtvtoperr_lineno
				errlnoidt := newIdent("errlno")
				errlnoidt.Obj = ast.NewObj(ast.Var, errlnoidt.Name)
				errlnoidt.NamePos = te.Pos()
				assign.Lhs = append(assign.Lhs, errlnoidt)
				assign.Rhs = append(assign.Rhs, errlnovarobj)

			case *ast.SwitchStmt:
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}

/*
遍历时即时修改问题太多

第二种方法，
* 遍历时记录当前修改的expr，及在其之前最近的可插入点要插入的exprs，这一步不做插入操作。
* 第二遍遍历做flat cursor树快照
* 第三遍遍历做插入操作，根据快照向上回溯找最近可插入点
*/

// TODO too too slow
// 在 check 之前做 ast 修改，不需要涉及类型问题
func (pc *ParserContext) walkpass_dotransforms(afterchk bool) {
	btime := time.Now()
	totcnt := 0
	for i := 0; ; i++ {
		addtot := pc.walkpass_dotransforms_impl(afterchk, i)
		totcnt += addtot
		if addtot == 0 {
			log.Println("total cycles", pc.bdpkgs.Name,
				i+1, addtot, totcnt, time.Since(btime))
			break
		}
		// log.Println("continue", i, addtot)
	}
}
func (pc *ParserContext) walkpass_dotransforms_impl(afterchk bool, cycle int) int {
	pkgs := pc.pkgs

	addlines := map[ast.Node][]ast.Stmt{} // step1 ast.Expr =>
	addtot := 0
	mrgaddlines := func(lines map[ast.Node][]ast.Stmt) {
		for e, stmts := range lines {
			addlines[e] = append(addlines[e], stmts...)
			addtot += len(stmts)
		}
	}

	// 遍历，耗时还是挺多的，和transforms的个数有关
	btime := time.Now()
	tfctx := &TransformContext{}
	tfctx.cycle = cycle
	for _, pkg := range pkgs {
		for _, fio := range pkg.Files {
			tfctx.fio = fio
			astutil.Apply(fio, func(c *astutil.Cursor) bool {
				tfctx.reset(pc, c, true)
				for _, tfo := range transforms {
					tfo.apply(tfctx)
				}
				if len(tfctx.inslines) > 0 {
					mrgaddlines(tfctx.inslines)
				}
				return true
			}, nil)
		}
	}

	dtime1 := time.Since(btime)
	btime = time.Now()
	pc.walkpass_flat_cursors()
	cursors := pc.cursors
	dtime2 := time.Since(btime)
	log.Println("addlines", pc.bdpkgs.Name, len(addlines), addtot, dtime1, dtime2)

	for curexpr, stmts := range addlines {
		_ = stmts
		c := cursors[curexpr]
		gopp.Assert(c != nil, "wtfff", curexpr)
		inscs := findupinsable(cursors, curexpr, 0)
		if inscs == nil {
			// TODO not found, but maybe global
			pn := c.Parent()
			te := curexpr
			log.Println("not found", reftyof(te), reftyof(pn), te, exprpos(pc, te))
		}
		InsertBefore(pc, c, inscs, stmts)
	}
	return addtot
}

func (pc *ParserContext) walkpass_flat_cursors() {
	pkgs := pc.pkgs
	cursors := map[ast.Node]*astutil.Cursor{}
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			tc := *c
			cursors[c.Node()] = &tc
			switch te := c.Node().(type) {
			case *ast.AssignStmt:
				if len(te.Rhs) > 0 {
					tetyx := pc.info.TypeOf(te.Lhs[0])
					tetyy := pc.info.TypeOf(te.Rhs[0])
					if false && tetyx == nil {
						log.Println(te.Lhs[0], tetyx, tetyy, exprpos(pc, te.Rhs[0]))
					}
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	pc.cursors = cursors
}

func (pc *ParserContext) walkpass_tmpl_proc() {
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			tc := *c
			pc.cursors[c.Node()] = &tc
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}

type tupleinfo struct {
	typ    *types.Tuple
	tyname string // genc struct name
	hash   string
	expr   ast.Expr
}

// extract multirets and map get tuple
func (pc *ParserContext) walkpass_tupletys() {
	tupletys := map[string]*tupleinfo{}
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Expr:
				tetyx := pc.info.TypeOf(te)
				var tupty *types.Tuple
				if tety, ok := tetyx.(*types.Signature); ok && tety.Results().Len() > 1 {
					tupty = tety.Results()
				} else if tety, ok := tetyx.(*types.Tuple); ok && tety.Len() > 0 {
					tupty = tety
				}
				if tupty != nil {
					tystr := tuptyhash(tupty)
					if _, ok2 := tupletys[tystr]; !ok2 {
						tupletys[tystr] = &tupleinfo{tupty, tmptyname(), tystr, te}
					}
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncDecl:
				if te.Type.Results.NumFields() < 2 {
					break
				}
				for idx, fld := range te.Type.Results.List {
					if len(fld.Names) == 0 {
						fld.Names = append(fld.Names, newIdent(tmpvarname2(idx)))
					}
				}
				tetyx := pc.info.TypeOf(te.Name)
				tety := tetyx.(*types.Signature).Results()
				tystr := tuptyhash(tety)
				if _, ok := tupletys[tystr]; !ok {
					tupletys[tystr] = &tupleinfo{tety, tmptyname(), tystr, te.Name}
				}
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("tupletys", pc.bdpkgs.Name, len(tupletys))
	pc.tupletys = tupletys
}

// 一句表达不了的表达式临时变量
func (pc *ParserContext) walkpass_tmpvars() {
	pkgs := pc.pkgs
	var tmpvars = map[ast.Stmt][]ast.Node{} // => tmpvarname
	gopp.G_USED(tmpvars)

	var lastblk *astutil.Cursor
	var lastst *astutil.Cursor
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Stmt:
				if c.Index() >= 0 {
					c1 := *c
					lastst = &c1 //
				}
			case *ast.BlockStmt:
				c1 := *c
				lastblk = &c1

			case *ast.CallExpr:
			default:
				// log.Println(c.Name(), exprpos(pc, c.Node()))
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.CompositeLit:
				break
				ce := c.Node().(ast.Expr)
				vsp2 := &ast.AssignStmt{}
				vsp2.Lhs = []ast.Expr{newIdent(tmpvarname())}
				vsp2.Rhs = []ast.Expr{ce}
				xe := &ast.UnaryExpr{}
				xe.Op = token.AND
				xe.OpPos = c.Node().Pos()
				xe.X = ce
				vsp2.Rhs = []ast.Expr{xe}
				vsp2.Tok = token.DEFINE
				c.Replace(vsp2.Lhs[0])
				stmt := upfindstmt(pc, c, 0)
				tmpvars[stmt] = append(tmpvars[stmt], vsp2)
				tyval := types.TypeAndValue{}
				tyval.Type = pc.info.TypeOf(ce)
				tyval.Type = types.NewPointer(tyval.Type)
				pc.info.Types[vsp2.Lhs[0]] = tyval
				pc.info.Types[vsp2.Rhs[0]] = tyval
			case *ast.UnaryExpr:
				if te.Op == token.AND {
					if _, ok := te.X.(*ast.CompositeLit); ok {
						vsp2 := &ast.AssignStmt{}
						vsp2.Lhs = []ast.Expr{newIdent(tmpvarname())}
						vsp2.Rhs = []ast.Expr{te}
						vsp2.Tok = token.DEFINE
						vsp2.TokPos = c.Node().Pos()
						c.Replace(vsp2.Lhs[0])
						stmt := upfindstmt(pc, c, 0)
						tmpvars[stmt] = append(tmpvars[stmt], vsp2)
						tyval := types.TypeAndValue{}
						tyval.Type = pc.info.TypeOf(te)
						pc.info.Types[vsp2.Lhs[0]] = tyval
					}
				}
			case *ast.CallExpr:
				for idx, aex := range te.Args {
					switch ae := aex.(type) {
					case *ast.BasicLit:
						vsp2 := &ast.AssignStmt{}
						vsp2.Lhs = []ast.Expr{newIdent(tmpvarname())}
						vsp2.Rhs = []ast.Expr{ae}
						vsp2.Tok = token.DEFINE
						vsp2.TokPos = c.Node().Pos()
						te.Args[idx] = vsp2.Lhs[0]
						// c.Replace(vsp2.Lhs[0])
						stmt := upfindstmt(pc, c, 0)
						tmpvars[stmt] = append(tmpvars[stmt], vsp2)
						tyval := types.TypeAndValue{}
						tyval.Type = pc.info.TypeOf(ae)
						pc.info.Types[vsp2.Lhs[0]] = tyval
					case *ast.CallExpr:
						vsp2 := &ast.AssignStmt{}
						vsp2.Lhs = []ast.Expr{newIdent(tmpvarname())}
						vsp2.Rhs = []ast.Expr{ae}
						vsp2.Tok = token.DEFINE
						vsp2.TokPos = c.Node().Pos()
						te.Args[idx] = vsp2.Lhs[0]
						// c.Replace(vsp2.Lhs[0])
						stmt := upfindstmt(pc, c, 0)
						tmpvars[stmt] = append(tmpvars[stmt], vsp2)
						tyval := types.TypeAndValue{}
						tyval.Type = pc.info.TypeOf(ae)
						pc.info.Types[vsp2.Lhs[0]] = tyval

					default:
						// log.Println(ae, reftyof(ae))
					}
				}
				// 检查函数调用返回值是否需要补齐
				stmt := upfindstmt(pc, c, 0)
				switch upstmt := stmt.(type) {
				case *ast.ExprStmt: // 无赋值
					retyx := pc.info.TypeOf(te)
					if retyx == nil {
						log.Println("todo notype", te.Fun)
						break
					}
					if isctydeftype2(retyx) {
						log.Println("todo incomplete ctype", retyx, te.Fun)
						break
					}
					vsp2 := &ast.AssignStmt{}
					vsp2.Rhs = []ast.Expr{te}
					vsp2.Tok = token.DEFINE
					vsp2.TokPos = c.Node().Pos()
					if rety, ok := retyx.(*types.Tuple); ok {
						if rety.Len() == 0 {
							// 空返回值被识别为types.Tuple?
							break
						}
						for i := 0; i < rety.Len(); i++ {
							vsp2.Lhs = append(vsp2.Lhs, newIdent(tmpvarname()))
							pc.info.Types[vsp2.Lhs[i]] = newtyandval(rety.At(i).Type())
						}
					} else {
						fntyx := pc.info.TypeOf(te.Fun)
						if fnty, ok := fntyx.(*types.Signature); ok {
							if fnty.Results() == nil || fnty.Results().Len() == 0 {
								break
							}
						}
						lexpr := newIdent(tmpvarname())
						vsp2.Lhs = []ast.Expr{lexpr}
						pc.info.Types[lexpr] = newtyandval(pc.info.TypeOf(te))
					}
					stmtc := pc.cursors[stmt]
					stmtp := stmtc.Parent()
					if blkstmt, ok := stmtp.(*ast.BlockStmt); ok {
						for idx, be := range blkstmt.List {
							if be == stmt {
								// str := fmt.Sprintf("%v", te.Fun)
								// log.Println("stmtp", reftyof(stmtp), te.Fun, str, idx)
								tmpvars[vsp2] = tmpvars[stmt]
								delete(tmpvars, stmt)
								blkstmt.List[idx] = vsp2
								break
							}
						}
					}
				default:
					_ = upstmt
				}
			case *ast.AssignStmt: // processing _ name
				// TODO depcreated
				for idx, ae := range te.Lhs {
					aidt, ok := ae.(*ast.Ident)
					if !ok {
						continue
					}
					if aidt.Name == "_" {
						tidt := ast.NewIdent(tmpvarname())
						te.Lhs[idx] = tidt
						tyval := types.TypeAndValue{}
						tyval.Type = pc.info.TypeOf(te.Rhs[idx])
						if tyval.Type == nil {
							tyval.Type = types.Typ[types.Int]
						}
						pc.info.Types[te.Lhs[idx]] = tyval
						pc.info.Types[tidt] = tyval

						// stmt := upfindstmt(pc, c, 0)
						stmt := te
						valsp := &ast.ValueSpec{}
						valsp.Names = []*ast.Ident{tidt}
						valsp.Type = te.Rhs[idx]
						//valsp.Values = []ast.Expr{ast.NewIdent("nilptr")}
						tmpvars[stmt] = append(tmpvars[stmt], valsp)
						pc.cursors[valsp] = c
					}
				}
			case *ast.IndexExpr:
				stmt := upfindstmt(pc, c, 0)
				// dont left value
				if ae, ok := stmt.(*ast.AssignStmt); ok {
					leftval := false
					for _, be := range ae.Lhs {
						if be == te {
							leftval = true
							break
						}
					}
					if leftval {
						break
					}
				}

				vsp2 := &ast.AssignStmt{}
				vsp2.Lhs = []ast.Expr{newIdent(tmpvarname())}
				vsp2.Rhs = []ast.Expr{te}
				vsp2.Tok = token.DEFINE
				vsp2.TokPos = c.Node().Pos()
				// te.Args[idx] = vsp2.Lhs[0]
				c.Replace(vsp2.Lhs[0])
				tmpvars[stmt] = append(tmpvars[stmt], vsp2)
				tyval := types.TypeAndValue{}
				tyval.Type = pc.info.TypeOf(te)
				pc.info.Types[vsp2.Lhs[0]] = tyval
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}

	log.Println("tmpvars", pc.bdpkgs.Name, len(tmpvars))
	pc.tmpvars = tmpvars
	cnter := 0
	for stmt, _ := range tmpvars {
		log.Println("tmpvars", cnter, exprpos(pc, stmt))
		cnter++
		if cnter > 2 {
			break
		}
	}
}

func (pc *ParserContext) walkpass_kvpairs() {
	kvpairs := map[ast.Node]ast.Node{}
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.AssignStmt:
				if len(te.Lhs) > len(te.Rhs) {
					// multirets
					break
				}
				for idx, le := range te.Lhs {
					kvpairs[le] = te.Rhs[idx]
					kvpairs[te.Rhs[idx]] = le
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("kvpairs", len(kvpairs))
	pc.kvpairs = kvpairs
}

func (pc *ParserContext) walkpass_functypes() {
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case ast.Expr:
				switch ye := te.(type) {
				case *ast.FuncType:
					pn := c.Parent()
					if _, ok := pn.(*ast.FuncDecl); ok {
						break
					}
					log.Println(te, exprstr(te), reftyof(te), reftyof(pn), ye)
					pc.functypes[ye] = tmptyname()
				}

			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}

func (pc *ParserContext) walkpass_gostmt() {
	var gostmts = []*ast.GoStmt{}
	_ = gostmts

	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.GoStmt:
				gostmts = append(gostmts, te)
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("gostmts", len(gostmts))
	pc.gostmts = gostmts
}

func (pc *ParserContext) walkpass_chan_send_recv() {
	var chanops = []ast.Expr{}
	_ = chanops

	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.SendStmt:
				chanops = append(chanops, te.Chan)
			case *ast.UnaryExpr:
				if te.Op == token.ARROW {
					chanops = append(chanops, te.X)
				}
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("chanops", len(chanops))
	pc.chanops = chanops
}

func (pc *ParserContext) walkpass_closures() {
	var closures = []*ast.FuncLit{}
	_ = closures

	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncLit:
				closures = append(closures, te)
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("closures", len(closures))
	pc.closures = closures
}

// 删除一些调用go tool cgo命令时生成的不需要的符号
func (pc *ParserContext) walkpass_clean_cgodecl() {
	pkgs := pc.pkgs
	skipfds := []string{"_cgo_runtime_cgocallback", "_cgoCheckResult", "_cgoCheckPointer",
		"_Cgo_use", "_cgo_runtime_cgocall", "_Cgo_ptr", "_cgo_cmalloc", "runtime_throw",
		"_cgo_runtime_gostringn", "_cgo_runtime_gostring"}

	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {

			switch te := c.Node().(type) {
			case *ast.FuncDecl:

				if funk.Contains(skipfds, te.Name.Name) {
					c.Delete()
				}
			case *ast.ValueSpec:
				name := te.Names[0].Name
				if strings.HasPrefix(name, "__cgofn__cgo_") || strings.HasPrefix(name, "_cgo_") ||
					strings.HasPrefix(name, "_Ciconst_") || strings.HasPrefix(name, "_Cfpvar_") {
					c.Delete()
					break
				}
				tystr := types.ExprString(te.Type)
				if tystr == "syscall.Errno" || te.Names[0].Name == "_" {
					c.Delete()
					break
				}
			case *ast.CallExpr:
				if fe, ok := te.Fun.(*ast.Ident); ok {
					if fe.Name == "_Cgo_ptr" {
						c.Replace(newIdent(te.Args[0].(*ast.Ident).Name[11:]))
						break
					}
					if fe.Name == "_cgoCheckPointer" {
						// panic: Delete node not contained in slice
						// c.Delete()
						// break
					}
				}
			case *ast.Ident:
				if strings.HasPrefix(te.Name, "_Ciconst_") {
					c.Replace(newIdent(te.Name[9:]))
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
}

// todo
func (pc *ParserContext) walkpass_nested_func() {
}

// todo
func (pc *ParserContext) walkpass_nested_type() {
}

func (pc *ParserContext) walkpass_defers() {
	defers := []*ast.DeferStmt{}

	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.DeferStmt:
				defers = append(defers, te)
			case *ast.FuncDecl:
				if te.Type.Results.NumFields() == 0 {
					// if len(te.Body.List) == 0 {
					// 	retstmt := &ast.ReturnStmt{}
					// 	retstmt.Results = []ast.Expr{}
					// 	// retstmt.Return = te.Pos()
					// 	te.Body.List = append(te.Body.List, retstmt)
					// } else {
					// 	log.Println("hhh")
					// 	laststmt := te.Body.List[len(te.Body.List)-1]
					// 	log.Println(laststmt)
					// }
				}
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("defers", len(defers))
	pc.defers = defers
}

func (pc *ParserContext) walkpass_globvars() {
	globvars := []ast.Node{}
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.ValueSpec:
				for _, name := range te.Names {
					if isglobalid(pc, name) {
						globvars = append(globvars, te)
					}
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println("globvars", len(globvars))
	pc.globvars = globvars
}

func (pc *ParserContext) putTyperefDependcy(funame, tyname string) {

}

// name0: caller
// name1: callee
func (pc *ParserContext) putFuncCallDependcy(name0, name1 string) {
	if name0 == name1 {
		return
	}
	if _, ok := builtinfns[name1]; ok {
		return
	}
	n0, ok0 := pc.funcdeclNodes[name0]
	if !ok0 {
		n0 = pc.gb.MakeNode()
		*n0.Value = name0
		pc.funcdeclNodes[name0] = n0
	}
	n1, ok1 := pc.funcdeclNodes[name1]
	if !ok1 {
		n1 = pc.gb.MakeNode()
		*n1.Value = name1
		pc.funcdeclNodes[name1] = n1
	}
	// log.Println("adding", name0, n0.Value, "->", name1, n1.Value)
	err := pc.gb.MakeEdge(n1, n0)
	gopp.ErrPrint(err, name0, name1)
}

func (pc *ParserContext) getImportNameMap() map[string]string {
	pkgrenames := map[string]string{} // path => rename
	for pname, pkgo := range pc.pkgs {
		log.Println(pname, pkgo.Name, pkgo.Imports)
		for fname, fileo := range pkgo.Files {
			log.Println(fname, fileo.Imports)
			for _, declo := range fileo.Decls {
				ad, ok := declo.(*ast.GenDecl)
				if !ok {
					continue
				}
				for _, tspec := range ad.Specs {
					id, ok := tspec.(*ast.ImportSpec)
					if ok {
						log.Println(id.Name, id.Path)
						dirp := strings.Trim(id.Path.Value, "\"")
						if id.Name != nil {
							pkgrenames[dirp] = id.Name.Name
						} else {
							pkgrenames[dirp] = ""
						}
					}
				}
			}
		}
	}
	return pkgrenames
}

func (pc *ParserContext) walkpass_fnexcepts() {
	fnexcepts := map[*ast.FuncDecl]*FuncExceptions{}
	var curfd *ast.FuncDecl
	var curfe *FuncExceptions
	var incatchstmt bool
	pkgs := pc.pkgs
	for _, pkg := range pkgs {
		astutil.Apply(pkg, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncDecl:
				// log.Println("enter fd", te.Name)
				if te.Body == nil || len(te.Body.List) == 0 {
					break
				}
				curfd = te
				fnexc := &FuncExceptions{}
				fnexc.fndecl = curfd
				fnexc.gotolab = tmplabname()
				curfe = fnexc

			case *ast.CallExpr:
				fntyx := pc.info.TypeOf(te)
				if curfd == nil {
					log.Println(curfd, te, exprpos(pc, te))
				}
				reterr := false
				switch fnty := fntyx.(type) {
				case *types.Named:
					reterr = iserrorty2(fnty)
				case *types.Tuple:
					for i := 0; i < fnty.Len(); i++ {
						reterr = iserrorty2(fnty.At(i).Type())
						if reterr {
							break
						}
					}
				}
				if !reterr {
					break
				}
				exi := &ExceptionInfo{}
				exi.index = len(curfe.callexes)
				exi.gobaklab = tmplabname()
				exi.callexpr = te
				curfe.callexes = append(curfe.callexes, exi)
				log.Println(curfd.Name, te.Fun, len(fnexcepts))
			case *ast.CatchStmt:
				curfe.catchexprs = append(curfe.catchexprs, te)
				incatchstmt = true
			case *ast.BranchStmt:
				if !incatchstmt {
					break
				}
				if te.Tok == token.CONTINUE {
					as := &ast.AssignStmt{}
					as.Tok = token.ASSIGN
					as.Lhs = append(as.Lhs, newIdent("gxtvtoperr"))
					as.Rhs = append(as.Rhs, nilidtcon)
					c.InsertBefore(as)
				} else if te.Tok == token.BREAK {
				}
			default:
				gopp.G_USED(te)
			}
			return true
		}, func(c *astutil.Cursor) bool {
			switch te := c.Node().(type) {
			case *ast.FuncDecl:
				// log.Println("leave fd", te.Name)
				if te.Body == nil || len(te.Body.List) == 0 {
					break
				}
				if len(curfe.callexes) > 0 {
					fnexcepts[curfd] = curfe
					log.Println(curfd.Name, len(curfe.callexes), len(curfe.catchexprs))
				}
				curfd = nil
				curfe = nil
			case *ast.CatchStmt:
				incatchstmt = false
			default:
				gopp.G_USED(te)
			}
			return true
		})
	}
	log.Println(pc.bdpkgs.Name, "fnexcepts", len(fnexcepts))
	pc.fnexcepts = fnexcepts
}
